﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using Microsoft.Office.Core;
using Microsoft.Office.Interop.PowerPoint;
using Shape = Microsoft.Office.Interop.PowerPoint.Shape;

namespace Microsoft.Office.PowerPoint.STAMP.Core
{
    /// <summary>
    /// Represents a powerpoint slide containing media elements and captions.
    /// </summary>
    public class StampSlide
    {
        private readonly Slide _slide;

        /// <summary>
        /// Initializes a new instance of the <see cref="StampSlide"/> class.
        /// </summary>
        /// <param name="slide">The slide.</param>
        public StampSlide(Slide slide)
        {
            _slide = slide;
        }

        /// <summary>
        /// Gets the media book mark effects.
        /// </summary>
        public List<Effect> GetMediaBookMarkEffects(StampMediaShape mediaShape)
        {
            const MsoAnimTriggerType triggerType = MsoAnimTriggerType.msoAnimTriggerOnMediaBookmark;
            return _slide.TimeLine.InteractiveSequences.Cast<Sequence>()
                .SelectMany(sequenceItem => sequenceItem.Cast<Effect>()).Where(
                    effectItem => effectItem.Timing.TriggerType == triggerType && new StampCaptionShape(effectItem.Shape).MediaId == mediaShape.CaptionMediaId).ToList();
        }

        /// <summary>
        /// Gets the application.
        /// </summary>
        public Application Application
        {
            get { return _slide.Application; }
        }

        /// <summary>
        /// Gets the slide.
        /// </summary>
        public Slide Slide
        {
            get { return _slide; }
        }

        /// <summary>
        /// Gets the width.
        /// </summary>
        public float Width
        {
            get { return Application.ActivePresentation.PageSetup.SlideWidth; }
        }

        /// <summary>
        /// Adds the text box.
        /// </summary>
        /// <param name="position">The position.</param>
        /// <param name="caption">The caption.</param>
        public StampCaptionShape AddTextBox(RectangleF position, CaptionItem caption)
        {
            Shape textboxShape = UtilPowerPoint.AddTextboxShape(_slide, position.X, position.Y, position.Width, position.Height);
            StampCaptionShape stampCaptionShape = new StampCaptionShape(textboxShape) { Name = caption.Name, Text = caption.Text };
            return stampCaptionShape;
        }

        /// <summary>
        /// Finds the shape by media id.
        /// </summary>
        /// <param name="captionItemsList">The caption items list.</param>
        /// <param name="mediaId">The media id.</param>
        /// <returns></returns>
        public IEnumerable<StampCaptionShape> FindShapesByMediaId(CaptionItemsList captionItemsList, string mediaId)
        {
            return ShapeFinder.FindShapesByMediaId(_slide, mediaId, captionItemsList);
        }

        /// <summary>
        /// Determines whether a bookmark with the given name is used by any effects on this slide.
        /// </summary>
        public bool HasEffectForBookmark(string bookmarkName, StampMediaShape mediaShape)
        {
            return GetMediaBookMarkEffects(mediaShape).Any(EffectUsesBookMark(bookmarkName));
        }

        private static Func<Effect, bool> EffectUsesBookMark(string bookmarkName)
        {
            return effect => effect.Timing.TriggerType == MsoAnimTriggerType.msoAnimTriggerOnMediaBookmark
                             && effect.Timing.TriggerBookmark.Equals(bookmarkName);
        }

        /// <summary>
        /// Adds a new interactive sequence for animations.
        /// </summary>
        /// <returns></returns>
        public Sequence AddSequenceForAnimations()
        {
            return _slide.TimeLine.InteractiveSequences.Add();
        }

        /// <summary>
        /// Deletes the caption by internal id.
        /// </summary>
        /// <param name="captionInternalId">The caption internal id.</param>
        /// <param name="captionItemsList">The caption items list.</param>
        /// <param name="mediaShape">The media shape.</param>
        public void DeleteCaptionByInternalId(string captionInternalId, CaptionItemsList captionItemsList, StampMediaShape mediaShape)
        {
            IEnumerable<StampCaptionShape> captionShapes = FindShapesByMediaId(captionItemsList, mediaShape.CaptionMediaId);
            foreach (StampCaptionShape shape in captionShapes)
            {
                if (shape.InternalId.Equals(captionInternalId))
                {
                    // Animation
                    DeleteAnimations(shape.Name);

                    DeleteBookMarkIfSafe(shape.BookMarkEntryName, mediaShape);
                    DeleteBookMarkIfSafe(shape.BookMarkExitName, mediaShape);

                    // Shape
                    shape.Delete();
                }
            }
        }

        private void DeleteBookMarkIfSafe(string bookMarkName, StampMediaShape mediaShape)
        {
            if (!HasEffectForBookmark(bookMarkName, mediaShape))
                mediaShape.DeleteBookmarkByName(bookMarkName);
        }

        /// <summary>
        /// Deletes the animations for the given media id.
        /// </summary>
        public void DeleteAnimations(CaptionItemsList captionItems, string captionMediaId)
        {
            string[] shapeNames = FindShapesByMediaId(captionItems, captionMediaId).Select(a => a.Name).ToArray();
            DeleteAnimations(shapeNames);
        }

        private void DeleteAnimations(params string[] shapeNames)
        {
            const MsoAnimTriggerType triggerType = MsoAnimTriggerType.msoAnimTriggerOnMediaBookmark;
            for (int i = _slide.TimeLine.InteractiveSequences.Count; i >= 1; i--)
            {
                Sequence sequence = _slide.TimeLine.InteractiveSequences[i];
                for (int x = sequence.Count; x >= 1; x--)
                {
                    Effect effect = sequence[x];
                    if (effect.Timing.TriggerType == triggerType)
                    {
                        if (shapeNames.Contains(effect.Shape.Name))
                            effect.Delete();
                    }
                }
            }
        }

        /// <summary>
        /// Retrieves the captions on this slide for the given <see cref="StampMediaShape"/>.
        /// </summary>
        /// <param name="mediaShape">The media shape.</param>
        /// <returns></returns>
        public CaptionItemsList RetrieveCaptions(StampMediaShape mediaShape)
        {
            string xmlPartId = mediaShape.CustomXMLPartId;
            CustomXMLPart customXMLPart = Application.ActivePresentation.CustomXMLParts.SelectByID(xmlPartId);
            if (customXMLPart == null)
                return new CaptionItemsList { Id = mediaShape.CaptionMediaId };

            string serialisedObjectGraph = customXMLPart.XML;
            return CaptionItemsList.DeserialiseFromXml(serialisedObjectGraph);
        }

        /// <summary>
        /// Adds the custom XML part.
        /// </summary>
        /// <param name="xmlString">The XML string.</param>
        /// <returns></returns>
        public string AddCustomXmlPart(string xmlString)
        {
            Presentation presentation = _slide.Application.ActivePresentation;
            CustomXMLPart xmlPart = presentation.CustomXMLParts.Add(xmlString);

            return xmlPart.Id;
        }

        /// <summary>
        /// Toggles the auto start of the given <see cref="StampMediaShape"/>.
        /// </summary>
        /// <param name="selectedShape">The selected shape.</param>
        public void ToggleAutoStart(StampMediaShape selectedShape)
        {
            foreach (Effect effect in _slide.TimeLine.MainSequence)
            {
                if (selectedShape.HasAutoplayEffect(effect))
                {
                    effect.Delete();
                    return;
                }
            }
            selectedShape.AddAutoPlayEffect(_slide.TimeLine.MainSequence);
        }

        /// <summary>
        /// Determines whether the given shape has an auto play effect.
        /// </summary>
        public bool HasAutoPlayEffectFor(StampMediaShape stampMediaShape)
        {
            return _slide.TimeLine.MainSequence.Cast<Effect>().Any(stampMediaShape.HasAutoplayEffect);
        }
    }
}